/*
 * lexical.c
 *
 * Version 1.0
 * Author: rtp
 *
 * Description: lexical analyzer
 *
 */

#include <stdio.h>
#include <stdlib.h>

#include "lexical.h"
#include "token.h"
#include "./DataCarriers/hash.h"
#include "./DataCarriers/linked_list.h"

#define RESERVED_WORD_COUNT 10
#define MAX_WORD_SIZE 50

//global variable for file pointer
FILE* source_file;

//global to mark columm and line in the error msg
int columm, line;

char* next = "";
int isNumber = 0;
int isText = 0;

Token actual = NULL;
int hasToken = 0;

hash keywords;
hash identifiers;
hash sinais;

//reserved word table

void lx_init()
{
	line = columm = 0;
	keywords = hm_create();
	sinais = hm_create();
	identifiers = hm_create();

	//keywords
	hm_add(keywords, "and");
	hm_add(keywords, "break");
	hm_add(keywords, "do");
	hm_add(keywords, "else");
	hm_add(keywords, "elseif");
	hm_add(keywords, "end");
	hm_add(keywords, "false");
	hm_add(keywords, "for");
	hm_add(keywords, "function");
	hm_add(keywords, "if");
	hm_add(keywords, "in");
	hm_add(keywords, "local");
	hm_add(keywords, "nil");
	hm_add(keywords, "not");
	hm_add(keywords, "or");
	hm_add(keywords, "repeat");
	hm_add(keywords, "return");
	hm_add(keywords, "then");
	hm_add(keywords, "true");
	hm_add(keywords, "until");
	hm_add(keywords, "while");
	hm_add(keywords, "...");
	//sinais
	hm_add(sinais, ";");
	hm_add(sinais, ",");
	hm_add(sinais, ".");
	hm_add(sinais, "{");
	hm_add(sinais, "}");
	hm_add(sinais, "[");
	hm_add(sinais, "]");
	hm_add(sinais, "(");
	hm_add(sinais, ")");
	hm_add(sinais, ">");
	hm_add(sinais, "<");
	hm_add(sinais, ">=");
	hm_add(sinais, "<=");
	hm_add(sinais, "=");
	hm_add(sinais, "~=");
	hm_add(sinais, "==");
	hm_add(sinais, "+");
	hm_add(sinais, "-");
	hm_add(sinais, "*");
	hm_add(sinais, "/");
	hm_add(sinais, "%");
	hm_add(sinais, "^");
	hm_add(sinais, "#");
}


void lx_setFile(char* filename)
{
	source_file = fopen(filename, "r");
}

void lx_closeFile()
{
	fclose(source_file);
}

void lx_pushToken(Token t)
{
	actual = t;
	hasToken = 1;
}

//gives the next Token
Token lx_getToken()
{
	if(hasToken == 1)
	{
		hasToken = 0;
		return actual;
	}

	Token ret = malloc(sizeof(tokenStruct));
	int type;

	if(next == "")
		if(lx_hasNext() == 0)
			return NULL;

	if((type = hm_find(keywords, next)) >= 0)
	{
		ret->type = TK_RESEVED_WORD;
		ret->index = type;
	}
	else if((type = hm_find(sinais, next)) >= 0)
	{
		ret->type = TK_SINAL;
		ret->index = type;
	}
	else if((type = hm_find(identifiers, next)) >= 0)
	{
		ret->type = TK_IDENTIFIER;
		ret->index = type;
		ret->value = next;
	}
	else
	{
		if(isNumber == 1)
		{
			ret->type = TK_NUMBER;
			ret->index = atoi(next);
			isNumber = 0;
		}
		else if(isText == 1)
		{
			ret->type = TK_TEXT;
			ret->value = next;
			isText = 0;
		}
		else
		{
			hm_add(identifiers, next);
			ret->type = TK_IDENTIFIER;
			ret->index = hm_find(identifiers, next);
			ret->value = next;
		}
	}

	next = "";
	fflush(stdin);
	return ret;
}

//implementar aqui um substituto pra fgetc
//contando linha e colunas do codigo fonte
char getChar()
{
	char temp = fgetc(source_file);
	if(temp == '\n')
	{
		line++;
		columm = 0;
	}
	else
		columm++;
	return temp;
}

void unGetChar(char temp)
{
	ungetc(temp, source_file);
	if(temp == '\t')
	{
		columm = 0;
		line--;
	}
	else
		columm--;
}

char* getNextWord()
{
	char* actual = (char*)malloc(MAX_WORD_SIZE*sizeof(char));
	char* tmp = (char*)malloc(sizeof(char));
	char temp, temp2;
	int i = 0, state = 0;
	int done = 0;

	while(done == 0)
	{
		switch(state)
		{
		case 0:
			temp = getChar();

			//To mark the columm in case of error
			if (temp=='\n'){
				columm = 0;
				line++;
			}

			//avoid blanks
			while(temp == ' ' || temp == '\n' || temp == '\t')
				temp = getChar();

			if(temp == EOF)
				return "";

			tmp[0] = temp;

			if(temp >= '0' && temp <= '9') //numbers
				state = 1;
			else if((temp >= 'a' && temp <= 'z') || (temp >= 'A' && temp <= 'Z')) //letters
				state = 2;
			//todo acertar os casos que nao sao usados (++, --, /=, etc)
			else if((temp >= 0x3C && temp <= 0x3E) || 	// < = >
					temp == '*' || temp == '+' || 		// * +
					temp == '-' || temp == '/' ||		// - /
					temp == '!')
				state = 3; //cases == <= >= += *= -= /= ++ -- ->
			else if(temp == '"')
			{
				state = 4;
			}
			else if(hm_find(sinais, tmp) >= 0)
			{
				actual[0] = temp;
				actual[1] = '\0';
				done = 1;
			}
			else
			{
				fprintf(stderr,"lexical.c (189): fatal error: unrecognized symbol (%c), line %d\n",temp, line);
				exit(1);
			}

			break;
		case 1: //consider float as well
			while(temp >= '0' && temp <= '9') //numbers
			{
				actual[i++] = temp;
				temp = getChar();
			}
			actual[i] = '\0';
			unGetChar(temp); //push character back for actual reading
			done = 1;
			isNumber = 1;
			break;
		case 2:
			while((temp >= 'a' && temp <= 'z') || (temp >= 'A' && temp <= 'Z'))
			{
				actual[i++] = temp;
				temp = getChar();
			}
			actual[i] = '\0';
			unGetChar(temp); //push character back for actual reading
			done = 1;
			break;
		case 3:
			actual[i++] = temp;
			temp = getChar();
			switch(temp) //second char
			{
			case '=': // == <= >= += -= *= /=
				actual[i++] = temp;
				actual[i] = '\0';
				done = 1;
				break;
			case '+':
				unGetChar(temp);
				done = 1;
				actual[i] = '\0';
				break;
			case '-':
				unGetChar(temp);
				done = 1;
				actual[i] = '\0';
				break;
			case '>': //case ->
				if(actual[i-1] == '-') //comments
				{
					actual[i++] = '>';
					done = 1;
					actual[i] = '\0';
				}
				else
				{
					unGetChar(temp);
					done = 1;
				}
				break;
			case '-':
				if(actual[i-1] == '-') //comment or comment block --[[]]
				{
					temp = getChar();

					if (temp == '[')
						if (getChar() == '[') //Comment Block
						{
							temp = getChar();
							while (temp != ']' || temp2 != ']') //looking for )!
							{
								temp2 = temp;
								temp = getChar();
							}

						} else {
							while (temp != '\n') //Until the end of the line
								continue;
							temp = getChar();
						}
					state = 0;
					i = 0;
					break;
				}
				else
				{
					unGetChar(temp);
					done = 1;
				}
				break;
			case '*':
				unGetChar(temp);
				done = 1;
				break;
			default:
				unGetChar(temp);
				done = 1;
				break;
			}
			break;
			case 4:
				actual[0] = temp;
				temp = getChar();
				while(temp != '"')
				{
					actual[i++] = temp;
					temp = getChar();
				}
				actual[i] = '\0';
				done = 1;
				isText = 1;
				break;
		}
	}

	return actual;
}

int lx_hasNext()
{
	if(next != "")
		return 1;

	next = getNextWord();

	printf("nextword: (%s): \n", next);
	if(next != "")
		return 1;

	return 0;
}
